/* * Copyright (c) 2012-2015, Microsoft Mobile * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.juniversal.translator.cplusplus; import java.util.ArrayList; import java.util.List; import org.juniversal.translator.core.*; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeParameter; public class HeaderTypeDeclarationWriter extends CPlusPlusASTNodeWriter<TypeDeclaration> { private TypeDeclaration typeDeclaration; private int typeIndent; private boolean outputSomethingForType; public HeaderTypeDeclarationWriter(CPlusPlusSourceFileWriter sourceFileWriter) { super(sourceFileWriter); } public void write(TypeDeclaration typeDeclaration) { this.typeDeclaration = typeDeclaration; // Skip the modifiers and the space/comments following them skipModifiers(typeDeclaration.modifiers()); skipSpaceAndComments(); // Remember how much the type is indented (typically only nested types are indented), so we can use that in // determining the "natural" indent for some things inside the type declaration. typeIndent = getTargetWriter().getCurrColumn(); boolean isInterface = typeDeclaration.isInterface(); @SuppressWarnings("unchecked") List<TypeParameter> typeParameters = (List<TypeParameter>) typeDeclaration.typeParameters(); boolean isGeneric = !typeParameters.isEmpty(); if (isGeneric) { write("template "); //sCPlusPlusASTWriter.writeTypeParameters(typeParameters, true); write(" "); } if (isInterface) matchAndWrite("interface", "class"); else matchAndWrite("class"); copySpaceAndComments(); matchAndWrite(typeDeclaration.getName().getIdentifier()); // Skip past the type parameters if (isGeneric) { setPosition(ASTUtil.getEndPosition(typeParameters)); skipSpaceAndComments(); match(">"); } writeSuperClassAndInterfaces(); copySpaceAndComments(); matchAndWrite("{"); copySpaceAndCommentsUntilEOL(); writeln(); // sourceFileWriter.getTargetWriter().incrementByPreferredIndent(); outputSomethingForType = false; writeNestedTypes(); writeMethods(); writeSuperDefinition(); writeFields(); writeSpaces(typeIndent); write("};"); setPosition(ASTUtil.getEndPosition(typeDeclaration)); } private void writeSuperClassAndInterfaces() { Type superclassType = typeDeclaration.getSuperclassType(); @SuppressWarnings("unchecked") List<Type> superInterfaceTypes = (List<Type>) typeDeclaration.superInterfaceTypes(); if (superclassType == null) write(" : public Object"); else { copySpaceAndComments(); matchAndWrite("extends", ": public"); copySpaceAndComments(); writeNode(superclassType); } // Write out the super interfaces, if any boolean firstInterface = true; for (Object superInterfaceTypeObject : superInterfaceTypes) { Type superInterfaceType = (Type) superInterfaceTypeObject; if (firstInterface) { skipSpaceAndComments(); matchAndWrite("implements", ", public"); } else { copySpaceAndComments(); matchAndWrite(",", ", public"); } // Ensure there's at least a space after the "public" keyword (not required in Java // which just has the comma there) int originalPosition = getPosition(); copySpaceAndComments(); if (getPosition() == originalPosition) write(" "); writeNode(superInterfaceType); firstInterface = false; } } @SuppressWarnings("unchecked") private void writeNestedTypes() { ArrayList<TypeDeclaration> publicTypes = new ArrayList<>(); ArrayList<TypeDeclaration> protectedTypes = new ArrayList<>(); ArrayList<TypeDeclaration> privateTypes = new ArrayList<>(); for (BodyDeclaration bodyDeclaration : (List<BodyDeclaration>) typeDeclaration.bodyDeclarations()) { if (bodyDeclaration instanceof TypeDeclaration) { TypeDeclaration nestedTypeDeclaration = (TypeDeclaration) bodyDeclaration; AccessLevel accessLevel = ASTUtil.getAccessModifier(nestedTypeDeclaration.modifiers()); if (accessLevel == AccessLevel.PUBLIC || accessLevel == AccessLevel.PACKAGE) publicTypes.add(nestedTypeDeclaration); else if (accessLevel == AccessLevel.PROTECTED) protectedTypes.add(nestedTypeDeclaration); else privateTypes.add(nestedTypeDeclaration); } } writeNestedTypeDeclarationsForAccessLevel(publicTypes, AccessLevel.PUBLIC); writeNestedTypeDeclarationsForAccessLevel(protectedTypes, AccessLevel.PROTECTED); writeNestedTypeDeclarationsForAccessLevel(privateTypes, AccessLevel.PRIVATE); } private void writeNestedTypeDeclarationsForAccessLevel(List<TypeDeclaration> typeDeclarations, AccessLevel accessLevel) { if (typeDeclarations.size() == 0) return; // If we've already output something for the class, add a blank line separator if (outputSomethingForType) writeln(); writeAccessLevelGroup(accessLevel, " // Nested class(es)"); outputSomethingForType = true; for (TypeDeclaration nestedTypeDeclaration : typeDeclarations) { setPositionToStartOfNodeSpaceAndComments(nestedTypeDeclaration); copySpaceAndComments(); writeNode(nestedTypeDeclaration); // Copy any trailing comment associated with the class, on the same line as the closing // brace; rare but possible copySpaceAndCommentsUntilEOL(); writeln(); } } private void writeAccessLevelGroup(AccessLevel accessLevel, String headerComment) { writeSpaces(typeIndent); String headerText; if (accessLevel == AccessLevel.PUBLIC || accessLevel == AccessLevel.PACKAGE) headerText = "public:"; else if (accessLevel == AccessLevel.PROTECTED) headerText = "protected:"; else if (accessLevel == AccessLevel.PRIVATE) headerText = "private:"; else throw new JUniversalException("Unknown access level: " + accessLevel); write(headerText); if (headerComment != null) write(headerComment); writeln(); } private void writeMethods() { ArrayList<MethodDeclaration> publicMethods = new ArrayList<>(); ArrayList<MethodDeclaration> protectedMethods = new ArrayList<>(); ArrayList<MethodDeclaration> privateMethods = new ArrayList<>(); for (Object bodyDeclaration : typeDeclaration.bodyDeclarations()) { if (bodyDeclaration instanceof MethodDeclaration) { MethodDeclaration MethodDeclaration = (MethodDeclaration) bodyDeclaration; AccessLevel accessLevel = ASTUtil.getAccessModifier(MethodDeclaration.modifiers()); if (accessLevel == AccessLevel.PUBLIC || accessLevel == AccessLevel.PACKAGE) publicMethods.add(MethodDeclaration); else if (accessLevel == AccessLevel.PROTECTED) protectedMethods.add(MethodDeclaration); else privateMethods.add(MethodDeclaration); } else if (!(bodyDeclaration instanceof FieldDeclaration || bodyDeclaration instanceof TypeDeclaration)) throw new JUniversalException("Unexpected bodyDeclaration type" + bodyDeclaration.getClass()); } writeMethodsForAccessLevel(publicMethods, AccessLevel.PUBLIC); writeMethodsForAccessLevel(protectedMethods, AccessLevel.PROTECTED); writeMethodsForAccessLevel(privateMethods, AccessLevel.PRIVATE); } private void writeMethodsForAccessLevel(List<MethodDeclaration> methodDeclarations, AccessLevel accessLevel) { if (methodDeclarations.size() == 0) return; // If we've already output something for the class, add a blank line separator if (outputSomethingForType) writeln(); writeAccessLevelGroup(accessLevel, null); outputSomethingForType = true; for (MethodDeclaration methodDeclaration : methodDeclarations) { // When writing the class definition, don't include method comments. So skip over the // Javadoc, which will just be by the implementation. setPositionToStartOfNode(methodDeclaration); // If there's nothing else on the line before the method declaration besides whitespace, // then indent by that amount; that's the typical case. However, if there's something on // the line before the method (e.g. a comment or previous declaration of something else // in the class), then the source is in some odd format, so just ignore that and use the // standard indent. skipSpacesAndTabsBackward(); if (getSourceLogicalColumn() == 0) copySpaceAndComments(); else { writeSpaces(getPreferredIndent()); skipSpacesAndTabs(); } writeNode(methodDeclaration); // Copy any trailing comment associated with the method; that's sometimes there for // one-liners copySpaceAndCommentsUntilEOL(); writeln(); } } private void writeSuperDefinition() { // If we've already output something for the class, add a blank line separator if (outputSomethingForType) writeln(); writeAccessLevelGroup(AccessLevel.PRIVATE, null); writeSpaces(typeIndent + getPreferredIndent()); write("typedef "); Type superclassType = typeDeclaration.getSuperclassType(); if (superclassType == null) write("Object"); else writeNodeAtDifferentPosition(superclassType); writeln(" super;"); } private void writeFields() { AccessLevel lastAccessLevel = null; for (Object bodyDeclaration : typeDeclaration.bodyDeclarations()) { if (bodyDeclaration instanceof FieldDeclaration) { FieldDeclaration fieldDeclaration = (FieldDeclaration) bodyDeclaration; AccessLevel accessLevel = ASTUtil.getAccessModifier(fieldDeclaration.modifiers()); // Write out the access level header, if it's changed boolean firstItemForAccessLevel = accessLevel != lastAccessLevel; if (firstItemForAccessLevel) { // If we've already output something for the class, add a blank line separator if (outputSomethingForType) writeln(); writeAccessLevelGroup(accessLevel, " // Data"); } // Skip back to the beginning of the comments, ignoring any comments associated with // the previous node setPosition(fieldDeclaration.getStartPosition()); skipSpaceAndCommentsBackward(); skipSpaceAndCommentsUntilEOL(); skipNewline(); if (firstItemForAccessLevel) skipBlankLines(); copySpaceAndComments(); // If the member is on the same line as other members, for some unusual reason, then // the above code won't indent it. So indent it here since in our output every // member/method is on it's own line in the class definition. if (getSourceLogicalColumn() == 0) writeSpaces(getPreferredIndent()); writeNode(fieldDeclaration); copySpaceAndCommentsUntilEOL(); writeln(); outputSomethingForType = true; lastAccessLevel = accessLevel; } } } }